#if !defined(UNICODE) || !defined(_UNICODE)
#define UNICODE
#define _UNICODE
#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500 // client works on 2000
#endif

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>

struct ThreadParams
{
	HANDLE hExitEvent;
	DWORD procID;
};

DWORD WINAPI ReadFromParent(LPVOID lpParams)
{
	ThreadParams* tp = static_cast<ThreadParams*>(lpParams);
	// open the un-redirected standard console streams 
	HANDLE hConOut = CreateFile(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	HANDLE hConIn = CreateFile(L"CONIN$", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if((hConIn != INVALID_HANDLE_VALUE) && (hConOut != INVALID_HANDLE_VALUE))
	{
		// create the read event to be used by overlapped io
		WCHAR eventName[20];
		wsprintf(eventName, L"readpid%d", tp->procID);
		HANDLE hReadEvent = CreateEvent(NULL, TRUE, FALSE, eventName);
		// check we got it
		if(hReadEvent)
		{
			// grab the redirected handles
			// - the read end of the output pipe (so we can read data to write to stdout from the parent)
			// - the write end of the input pipe (so we can write user stdin-put to the parent)
			HANDLE hRedirOut = GetStdHandle(STD_OUTPUT_HANDLE);
			HANDLE hRedirIn = GetStdHandle(STD_INPUT_HANDLE);

			// make sure the pipe handle is in the right mode
			DWORD mode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
			SetNamedPipeHandleState(hRedirIn, &mode, NULL, NULL);
			// set conin and conout as the standard handles
			SetStdHandle(STD_OUTPUT_HANDLE, hConOut);
			SetStdHandle(STD_INPUT_HANDLE, hConIn);

			{
				// inform the parent of the console window handle
				DWORD dwBytesWritten = 0;
				HWND hwnd = GetConsoleWindow();
				WriteFile(hRedirIn, &hwnd, sizeof(hwnd), &dwBytesWritten, NULL);
				FlushFileBuffers(hRedirIn);
			}

			// make sure the new output is the active display buffer
			SetConsoleActiveScreenBuffer(hConOut);

			// params for reading from the redirected in
			char parentBuffer[256];
			DWORD parentRead = 0;
			OVERLAPPED parentReadOL = {0};
			parentReadOL.hEvent = hReadEvent;

			// conin reads
			char coninBuffer[256];
			DWORD coninRead = 0;

			// waitable objects
			const HANDLE hWaitObjects[] = {tp->hExitEvent, hReadEvent, hConIn};
			// set off the async read
			// reading from out seems counter intuitive, but hRedirOut is the handle
			// that the parent writes data to which it expects to display as stdout
			// we have to read this, and write it to stdout explicitly
			ReadFile(hRedirOut, parentBuffer, 1, &parentRead, &parentReadOL);
			for(;;)
			{
				// wait on our handles
				DWORD ret = WaitForMultipleObjects(ARRAYSIZE(hWaitObjects), hWaitObjects, FALSE, INFINITE);
				switch(ret)
				{
					// exit event fired, cancel the async IO and exit the loop
					case WAIT_OBJECT_0:
					{
						CancelIo(hRedirOut);
						goto loopend;
					}
					break;
					// parent wrote to stdout pipe
					case WAIT_OBJECT_0 + 1:
					{
						// clear the event status
						ResetEvent(hReadEvent);
						// write the odd byte we read
						WriteFile(hConOut, parentBuffer, 1, &parentRead, NULL);
						// see how much is in the pipe
						DWORD avail = 0;
						PeekNamedPipe(hRedirOut, NULL, 0, NULL, &avail, NULL);
						// write it out
						while(avail != 0)
						{
							ReadFile(hRedirOut, parentBuffer, min(avail, sizeof(parentBuffer)), &parentRead, NULL);
							avail -= parentRead;
							WriteFile(hConOut, parentBuffer, parentRead, &parentRead, NULL);
						}
						// start off the async read again
						ReadFile(hRedirOut, parentBuffer, 1, &parentRead, &parentReadOL);
					}
					break;
					// there are console input events
					case WAIT_OBJECT_0 + 2:
					{
						// unfortunately there are no facilities for async reads of
						// keyboard inputs to the console, so this is just one huge workaround
						bool wroteSome = false;
						for(;;)
						{
							// flag indicating we've seen a key input
							bool hasKeyboardEvents = false;
							// the events
							INPUT_RECORD records[16];
							const DWORD numRecords = ARRAYSIZE(records);
							DWORD eventsInBuffer = 0;
							// see how many events there are
							PeekConsoleInput(hConIn, records, numRecords, &eventsInBuffer);
							// see if any of them are keyboard events
							for(DWORD i = 0; i < eventsInBuffer; ++i)
							{
								if(records[i].EventType == KEY_EVENT)
								{
									hasKeyboardEvents = true;
									break;
								}
							}
							// if we got keyboard events read them
							// We have to check for keyboard inputs rather than going
							// straight to ReadFile since it will block if there aren't any,
							// which obviously doesn't go well with waiting for the other events
							if(hasKeyboardEvents)
							{
								ReadFile(hConIn, coninBuffer, sizeof(coninBuffer), &coninRead, NULL);
								WriteFile(hRedirIn, coninBuffer, coninRead, &coninRead, NULL);
								wroteSome = true;
							}
							// if there were input events but not keyboard ones
							// read and discard them
							else if(eventsInBuffer)
							{
								ReadConsoleInput(hConIn, records, numRecords, &eventsInBuffer);
							}
							// if there are no events go back to waiting on the handle
							else break;
						}
						// if there were some input events, make sure the parent
						// can read them, by flushing the pipes buffer
						if(wroteSome)
						{
							FlushFileBuffers(hRedirIn);
						}
					}
					break;
					// not interested if anything else happens so tell the optimizer to not
					// generate any code to handle it
					default: __assume(0);
				}
			}
loopend:
			// reset the standard handles to what they were 
			// so the system can do what it does with them
			SetStdHandle(STD_OUTPUT_HANDLE, hRedirOut);
			SetStdHandle(STD_INPUT_HANDLE, hRedirIn);
			CloseHandle(hReadEvent);
		}
		CloseHandle(hConOut);
	}
	else
	{
		if(hConIn != INVALID_HANDLE_VALUE)
		{
			CloseHandle(hConIn);
		}
	}
	return 0;
}

int EntryFunc()
{
	// create the exit event
	WCHAR eventName[30];
	const DWORD pid = GetCurrentProcessId();
	wsprintf(eventName, L"pid%d", pid);
	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, eventName);
	if(hEvent)
	{
		ThreadParams tp = {hEvent, pid};
		HANDLE hReadThread = CreateThread(NULL, 1024, &ReadFromParent, &tp, 0, NULL);
		if(hReadThread)
		{
			HANDLE hWaitables[] = {hEvent, hReadThread};
			const DWORD numWaitables = ARRAYSIZE(hWaitables);
			// keep waiting on the handles until they've both been set
			// in case we need to extend it in future, use an alertable wait
			while(WaitForMultipleObjectsEx(numWaitables, hWaitables, TRUE, INFINITE, TRUE) >= (numWaitables - WAIT_OBJECT_0));
			CloseHandle(hReadThread);
		}
		CloseHandle(hEvent);
	}
	return 0;
}